﻿/* 2012/4/18 */
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using System.Windows.Forms;
using Cosmos.IO.FileSystemPath.Searcher;

namespace Cosmos.IO.FileSystemPath.Provider
{
    /// <summary>
    /// Generate file paths by specifying search condition.
    /// </summary>
    public class FilePathProvider
    {
        /// <summary>
        /// Represents information of searching file path.
        /// </summary>
        private struct SearchInfo 
        {
            public string FolderPath
            {
                get;
                set;
            }

            public string SearchPattern
            {
                get;
                set;
            }

            public SearchOption SearchOption
            {
                get;
                set;
            }

            public SearchFileDirectoryOption SearchFileDirectoryOption
            {
                get;
                set;
            }

            public bool InvertMatch
            {
                get;
                set;
            }
        }

        private const int _TIMER_INVOCATION_INTERVAL = 1000;

        /// <summary>
        /// All file paths of specified folder path matching the given search pattern.
        /// </summary>
        private List<string> _filePaths;

        /// <summary>
        /// Buffer of added paths.
        /// To avoid FilePathAdded event occurs too frequently,
        /// this event is triggered by a timer.
        /// Every time some paths are added, event will not be triggered,
        /// these paths will be stored in this buffer and wait for the
        /// trigger time.
        /// </summary>
        private List<string> _filePathBuffer;
        
        /// <summary>
        /// The path of folder to search.
        /// </summary>
        private string _folderPath;

        /// <summary>
        /// The search string used to search files.
        /// </summary>
        private string _searchPattern;

        /// <summary>
        /// One of the values of the SearchOption enumeration that specifies
        /// whether the search operation should include only the current directory
        /// or should include all subdirectories.
        /// </summary>
        private SearchOption _searchOption;

        private SearchFileDirectoryOption _searchFileDirectoryOption;

        private bool _invertMatch;

        /// <summary>
        /// An file path searcher that search files from a directory.
        /// </summary>
        private IPathSearcher _searcher;

        /// <summary>
        /// Loads file paths on a separate thread.
        /// </summary>
        private BackgroundWorker _searchPathBackgroundWorker;

        private Timer _addPathTimer;

        /// <summary>
        /// Initialize a instance of FilePathProvider.
        /// </summary>
        public FilePathProvider()
        {
            _searchOption = SearchOption.TopDirectoryOnly;
            _searchFileDirectoryOption = SearchFileDirectoryOption.File;
            _invertMatch = false;

            _filePaths = new List<string>();
            _filePathBuffer = new List<string>();

            _searcher = FilePathSearcherFactory.CreateSearcher();

            _searchPathBackgroundWorker = new BackgroundWorker()
            {
                WorkerReportsProgress = true,
                WorkerSupportsCancellation = true,
            };
            _searchPathBackgroundWorker.DoWork += searchPathBackgroundWorker_DoWork;
            _searchPathBackgroundWorker.ProgressChanged += searchPathBackgroundWorker_ProgressChanged;
            _searchPathBackgroundWorker.RunWorkerCompleted += searchPathBackgroundWorker_RunWorkerCompleted;

            _addPathTimer = new Timer();
            _addPathTimer.Tick += delegate(object sender, EventArgs _e)
            {
                AddFilePathFromBuffer();
            };
            _addPathTimer.Interval = _TIMER_INVOCATION_INTERVAL;
        }

        private void AddFilePathFromBuffer()
        {
            lock (_filePathBuffer)
            {
                if (_filePathBuffer.Count != 0)
                {
                    OnFilePathAdded(new PathAddedEventArgs(_filePathBuffer));
                    _filePathBuffer.Clear();
                }
            }
        }

        /// <summary>
        /// Load file paths.
        /// </summary>
        /// <remarks>The folder path, search pattern and search option will not be changed.</remarks>
        public void Load()
        {
            OnFolderPathChanged(EventArgs.Empty);

            if (_searchPathBackgroundWorker.IsBusy)
            {
                _searchPathBackgroundWorker.CancelAsync();
            }
            else
            {
                _searchPathBackgroundWorker.RunWorkerAsync(GenerateSearchInfo());
                _addPathTimer.Start();
            }
        }

        /// <summary>
        /// Load file paths from the specified folder.
        /// </summary>
        /// <param name="selectedFolderPath">Folder path.</param>
        /// <remarks>The search pattern will not be changed by this method.</remarks>
        public void Load(string folderPath)
        {
            _folderPath = folderPath;
            Load();
        }

        /// <summary>
        /// Load file paths from the specified folder matching the given search pattern. 
        /// </summary>
        /// <param name="selectedFolderPath">Folder path.</param>
        /// <param name="searchPattern">Search pattern.</param>
        public void Load(string folderPath, string searchPattern)
        {
            _folderPath = folderPath;
            _searchPattern = searchPattern;
            Load();
        }

        protected void OnFolderPathChanged(EventArgs e)
        {
            if (FolderPathChanged != null)
                FolderPathChanged(this, e);
        }

        protected void OnFilePathAdded(PathAddedEventArgs e)
        {
            if (FilePathAdded != null)
                FilePathAdded(this, e);
        }

        protected void OnLoadCompleted(EventArgs e)
        {
            if (LoadCompleted != null)
                LoadCompleted(this, e);
        }

        private SearchInfo GenerateSearchInfo()
        {
            return GenerateSearchInfo(_folderPath, _searchPattern,
                _searchOption, _searchFileDirectoryOption, _invertMatch);
        }

        private SearchInfo GenerateSearchInfo(string folderPath, string searchPattern,
            SearchOption searchOption, SearchFileDirectoryOption searchFileDirectoryOption,
            bool invertMatch)
        {
            return new SearchInfo()
            {
                FolderPath = folderPath,
                SearchPattern = searchPattern,
                SearchOption = searchOption,
                SearchFileDirectoryOption = searchFileDirectoryOption,
                InvertMatch = invertMatch,
            };
        }

        private void SearchFilePath(SearchInfo searchInfo, Func<bool> isCanceled)
        {
            if (isCanceled())
                return;

            if (Directory.Exists(searchInfo.FolderPath))
            {
                string[] filePaths = _searcher.Search(searchInfo.SearchPattern,
                    searchInfo.FolderPath, searchInfo.SearchFileDirectoryOption,
                    searchInfo.InvertMatch);

                // Ensure the capacity of path list is enough for new files.
                lock (_filePaths)
                {
                    int needCapacity = _filePaths.Count + filePaths.Length;
                    if (_filePaths.Capacity < needCapacity)
                        _filePaths.Capacity = needCapacity;

                    _filePaths.AddRange(filePaths);
                }

                lock (_filePathBuffer)
                {
                    int needCapacity = _filePathBuffer.Count + filePaths.Length;
                    if (_filePathBuffer.Capacity < needCapacity)
                        _filePathBuffer.Capacity = needCapacity;

                    _filePathBuffer.AddRange(filePaths);
                }

                //searchBackgroundWorker.ReportProgress(0, filePaths);

                if (searchInfo.SearchOption == SearchOption.AllDirectories)
                {
                    // Search files in sub-folders.
                    DirectoryInfo directory = new DirectoryInfo(searchInfo.FolderPath);
                    DirectoryInfo[] directories = directory.GetDirectories();
                    foreach (DirectoryInfo aDirectory in directories)
                    {
                        SearchFilePath(
                            GenerateSearchInfo(aDirectory.FullName,
                                searchInfo.SearchPattern, searchInfo.SearchOption,
                                searchInfo.SearchFileDirectoryOption, searchInfo.InvertMatch),
                            isCanceled);
                    }
                }
            }
        }     

        private void searchPathBackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            BackgroundWorker self = sender as BackgroundWorker;

            lock (_filePaths)
            {
                _filePaths.Clear();
            }

            lock (_filePathBuffer)
            {
                _filePathBuffer.Clear();
            }
            
            SearchInfo searchInfo = (SearchInfo)e.Argument;
            SearchFilePath(searchInfo, () => self.CancellationPending);

            e.Result = searchInfo;
        }

        private void searchPathBackgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
        }

        private void searchPathBackgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            bool hasChanged = false;
            if (e.Result != null)
            {
                SearchInfo searchInfo = (SearchInfo)e.Result;
                if (searchInfo.FolderPath != (_folderPath)
                || searchInfo.SearchPattern != (_searchPattern)
                || searchInfo.SearchOption != _searchOption)
                    hasChanged = true;
            }

            if (e.Cancelled || hasChanged)
            {
                BackgroundWorker self = sender as BackgroundWorker;
                self.RunWorkerAsync(GenerateSearchInfo());
                _addPathTimer.Start();
            }
            else
            {
                _addPathTimer.Stop();
                AddFilePathFromBuffer();
                OnLoadCompleted(EventArgs.Empty);
            }
        }

        /// <summary>
        /// Gets or sets the path of folder to search.
        /// </summary>
        public string FolderPath
        {
            get
            {
                return _folderPath;
            }

            set
            {
                _folderPath = value;
            }
        }

        /// <summary>
        /// Gets or sets the search string used to search files.
        /// </summary>
        public string SearchPattern
        {
            get
            {
                return _searchPattern;
            }

            set
            {
                _searchPattern = value;
            }
        }

        /// <summary>
        /// Gets or sets one of the values of the SearchOption enumeration that specifies
        /// whether the search operation should include only the current directory
        /// or should include all subdirectories.
        /// </summary>
        public SearchOption SearchOption
        {
            get
            {
                return _searchOption;
            }

            set
            {
                _searchOption = value;
            }
        }

        public SearchFileDirectoryOption SearchFileDirectoryOption
        {
            get
            {
                return _searchFileDirectoryOption;
            }

            set
            {
                _searchFileDirectoryOption = value;
            }
        }

        public bool InvertMatch
        {
            get
            {
                return _invertMatch;
            }

            set
            {
                _invertMatch = value;
            }
        }

        /// <summary>
        /// Gets or sets file path searcher that searches file paths from a directory.
        /// </summary>
        public IPathSearcher Searcher
        {
            get 
            {
                return _searcher; 
            }

            set
            {
                _searcher = value; 
            }
        }

        /// <summary>
        /// Gets file paths provided by this FilePathProvider.
        /// </summary>
        public ReadOnlyCollection<string> FilePaths
        {
            get
            {
                return _filePaths.AsReadOnly();
            }
        }

        public bool IsBusy
        {
            get;
            private set;
        }

        /// <summary>
        /// Occurs when the folder path has been changed and the file path list will change.
        /// </summary>
        public event EventHandler FolderPathChanged;

        /// <summary>
        /// Occurs when the file paths has been added to the search result.
        /// </summary>
        public event PathAddedEventHandler FilePathAdded;

        /// <summary>
        /// Occurs when background work has completed,
        /// all file paths has been loaded.
        /// </summary>
        public event EventHandler LoadCompleted;
    }
}
